package org.apache.lucene.store;

/**
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;

/** Base implementation class for buffered {@link IndexInput}. */
public abstract class BufferedIndexInput extends IndexInput {
    static final int BUFFER_SIZE = BufferedIndexOutput.BUFFER_SIZE;

    private byte[] buffer;

    private long bufferStart = 0; // position in file of buffer
    private int bufferLength = 0; // end of valid bytes
    private int bufferPosition = 0; // next byte to read

    public byte readByte() throws IOException {
        if (bufferPosition >= bufferLength)
            refill();
        return buffer[bufferPosition++];
    }

    public void readBytes(byte[] b, int offset, int len) throws IOException {
        if (len < BUFFER_SIZE) {
            for (int i = 0; i < len; i++) // read byte-by-byte
                b[i + offset] = (byte) readByte();
        } else { // read all-at-once
            long start = getFilePointer();
            seekInternal(start);
            readInternal(b, offset, len);

            bufferStart = start + len; // adjust stream variables
            bufferPosition = 0;
            bufferLength = 0; // trigger refill() on read
        }
    }

    private void refill() throws IOException {
        long start = bufferStart + bufferPosition;
        long end = start + BUFFER_SIZE;
        if (end > length()) // don't read past EOF
            end = length();
        bufferLength = (int) (end - start);
        if (bufferLength <= 0)
            throw new IOException("read past EOF");

        if (buffer == null)
            buffer = new byte[BUFFER_SIZE]; // allocate buffer lazily
        readInternal(buffer, 0, bufferLength);

        bufferStart = start;
        bufferPosition = 0;
    }

    /** Expert: implements buffer refill.  Reads bytes from the current position
     * in the input.
     * @param b the array to read bytes into
     * @param offset the offset in the array to start storing bytes
     * @param length the number of bytes to read
     */
    protected abstract void readInternal(byte[] b, int offset, int length) throws IOException;

    public long getFilePointer() {
        return bufferStart + bufferPosition;
    }

    public void seek(long pos) throws IOException {
        if (pos >= bufferStart && pos < (bufferStart + bufferLength))
            bufferPosition = (int) (pos - bufferStart); // seek within buffer
        else {
            bufferStart = pos;
            bufferPosition = 0;
            bufferLength = 0; // trigger refill() on read()
            seekInternal(pos);
        }
    }

    /** Expert: implements seek.  Sets current position in this file, where the
     * next {@link #readInternal(byte[],int,int)} will occur.
     * @see #readInternal(byte[],int,int)
     */
    protected abstract void seekInternal(long pos) throws IOException;

    public Object clone() {
        BufferedIndexInput clone = (BufferedIndexInput) super.clone();

        if (buffer != null) {
            clone.buffer = new byte[BUFFER_SIZE];
            System.arraycopy(buffer, 0, clone.buffer, 0, bufferLength);
        }

        return clone;
    }

}
